package com.jh.fcsm.service.gen.impl;

import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.jh.fcsm.beans.gen.GenTable;
import com.jh.fcsm.beans.gen.GenTableColumn;
import com.jh.fcsm.beans.gen.vo.GenTableColumnVo;
import com.jh.fcsm.beans.gen.vo.GenTableVo;
import com.jh.fcsm.common.BaseServiceImpl;
import com.jh.fcsm.common.exception.ServiceException;
import com.jh.fcsm.config.GenConfig;
import com.jh.fcsm.constant.Constant;
import com.jh.fcsm.constant.GenConstants;
import com.jh.fcsm.dao.gen.GenTableColumnMapper;
import com.jh.fcsm.dao.gen.GenTableMapper;
import com.jh.fcsm.service.gen.GenTableService;
import com.jh.fcsm.service.gen.util.GenUtils;
import com.jh.fcsm.service.gen.util.VelocityInitializer;
import com.jh.fcsm.service.gen.util.VelocityUtils;
import com.jh.fcsm.util.security.UUIDUtils;
import com.jh.fcsm.util.txt.CharsetKit;
import com.jh.fcsm.util.txt.StringUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.entity.Example.Criteria;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成业务表
 *
 * @author szx
 * @date 2021-10-15 09:59:19
 */
@Service
@Transactional(readOnly = true)
public class GenTableServiceImpl extends BaseServiceImpl<GenTableMapper, GenTable> implements GenTableService {

    public static final Logger logger = LoggerFactory.getLogger(GenTableServiceImpl.class);

    @Autowired
    private GenTableMapper genTableMapper;
    @Autowired
    private GenTableColumnMapper genTableColumnMapper;

    @Autowired
    private GenConfig genConfig;
    /**
     * 获取代码生成地址
     *
     * @param table    业务表信息
     * @param template 模板文件路径
     * @return 生成地址
     */
    public static String getGenPath(GenTableVo table, String template) {
        String genPath = table.getGenPath();
        if (StringUtils.equals(genPath, "/")) {
            return System.getProperty("user.dir") + File.separator + "src" + File.separator
                    + VelocityUtils.getFileName(template, table);
        }
        return genPath + File.separator + VelocityUtils.getFileName(template, table);
    }

    @Override
    @Transactional(readOnly = false)
    /**
     *  保存或更新代码生成业务表
     * @param genTable 代码生成业务表对象
     * @return String 代码生成业务表ID
     * @author szx
     */
    public String saveOrUpdateGenTable(GenTable genTable) {
        if (genTable == null) {
            throw new ServiceException("数据异常");
        }
        if (StringUtils.isEmpty(genTable.getId())) {
            // 新增
            genTable.setId(UUIDUtils.getUUID());
            genTableMapper.insertSelective(genTable);
        } else {
            // 避免页面传入修改
            genTable.setYn(null);
            genTableMapper.updateByPrimaryKeySelective(genTable);
        }
        return genTable.getId();
    }

    @Override
    @Transactional(readOnly = false)
    /**
     *  删除代码生成业务表
     * @param id void 代码生成业务表ID
     * @author szx
     */
    public void deleteGenTable(String id) {
        // TODO 做判断后方能执行删除
        GenTable genTable = genTableMapper.selectByPrimaryKey(id);
        if (genTable == null) {
            throw new ServiceException("非法请求");
        }
        // 逻辑删除
        GenTable temgenTable = new GenTable();
        temgenTable.setYn(Constant.NO);
        temgenTable.setId(genTable.getId());
        genTableMapper.updateByPrimaryKeySelective(temgenTable);
    }

    /**
     * @param id
     * @return GenTable
     * 查询代码生成业务表详情
     * @author szx
     */
    @Override
    public GenTable findById(String id) {
        return genTableMapper.selectByPrimaryKey(id);
    }

    /**
     * @param genTableVo
     * @return PageInfo<GenTable>
     * 分页查询代码生成业务表
     * @author szx
     */
    @Override
    public PageInfo<GenTable> findPageByQuery(GenTableVo genTableVo) {
        PageHelper.startPage(genTableVo.getPageNum(), genTableVo.getPageSize());
        Example example = new Example(GenTable.class);
        Criteria criteria = example.createCriteria();
        criteria.andEqualTo("yn", Constant.YES);
        // 查询条件
        if (!StringUtils.isEmpty(genTableVo.getTableName())) {
            criteria.andLike("tableName", "%" + genTableVo.getTableName() + "%");
        }
        if (!StringUtils.isEmpty(genTableVo.getTableComment())) {
            criteria.andLike("tableComment", "%" + genTableVo.getTableComment() + "%");
        }
        List<GenTable> genTableList = genTableMapper.selectByExample(example);
        return new PageInfo<>(genTableList);
    }

    /**
     * 查询据库列表
     *
     * @param genTable 业务信息
     * @return 数据库表集合
     */
    @Override
    public PageInfo<GenTable> findDbTableList(GenTableVo genTable) {
        PageHelper.startPage(genTable.getPageNum(), genTable.getPageSize());
        List<GenTable> list = genTableMapper.findDbTableList(genTable);
        return new PageInfo<GenTable>(list);
    }

    /**
     * 查询据库列表
     *
     * @param tableNames 表名称组
     * @return 数据库表集合
     */
    @Override
    public List<GenTable> findDbTableListByNames(String[] tableNames) {
        return genTableMapper.findDbTableListByNames(tableNames);
    }

    /**
     * 导入表结构
     *
     * @param tableList 导入表列表
     */
    @Override
    @Transactional(readOnly = false)
    public void importGenTable(List<GenTable> tableList, String operName) {
        try {
            for (GenTable table : tableList) {
                String tableName = table.getTableName();
                GenUtils.initTable(table, operName,genConfig);
                table.setId(UUIDUtils.getUUID());
                int row = genTableMapper.insertSelective(table);
                if (row > 0) {
                    // 保存列信息
                    List<GenTableColumnVo> genTableColumns = genTableColumnMapper.findDbTableColumnsByName(tableName);
                    for (GenTableColumnVo column : genTableColumns) {
                        GenUtils.initColumnField(column, table);
                        column.setId(UUIDUtils.getUUID());
                        genTableColumnMapper.insertSelective(column);
                    }
                }
            }
        } catch (Exception e) {
            throw new ServiceException("导入失败：" + e.getMessage());
        }
    }

    /**
     * 预览代码
     *
     * @param tableId 表编号
     * @return 预览数据列表
     */
    @Override
    public Map<String, String> previewCode(String tableId) {
        Map<String, String> dataMap = new LinkedHashMap<>();
        // 查询表信息
        GenTable table = genTableMapper.selectByPrimaryKey(tableId);
        GenTableVo tableVo = new GenTableVo();
        BeanUtils.copyProperties(table, tableVo);
        //设置列信息
        setColumnTable(tableVo);

        // 设置主子表信息
        setSubTable(tableVo);
        // 设置主键列信息
        setPkColumn(tableVo);
        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(tableVo);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constant.UTF8);
            tpl.merge(context, sw);
            dataMap.put(template, sw.toString());
        }
        return dataMap;
    }

    /**
     * 设置列信息
     *
     * @param tableVo
     */
    private void setColumnTable(GenTableVo tableVo) {

        Example example = new Example(GenTableColumn.class);
        example.createCriteria().andEqualTo("tableId", tableVo.getId());
        example.orderBy("sort").asc();
        List<GenTableColumn> genTableColumnList = genTableColumnMapper.selectByExample(example);

        List<GenTableColumnVo> genTableColumnVoList = new ArrayList<>();
        tableVo.setColumns(genTableColumnVoList);
        if (CollectionUtils.isEmpty(genTableColumnList)) {
            return;
        }
        for (GenTableColumn column : genTableColumnList) {
            if(!GenConstants.BASE_ENTITY.contains(column.getJavaField())) {
                GenTableColumnVo columnVo = new GenTableColumnVo();
                BeanUtils.copyProperties(column, columnVo);
                genTableColumnVoList.add(columnVo);
            }
        }
    }

    /**
     * 生成代码（下载方式）
     *
     * @param tableName 表名称
     * @return 数据
     */
    @Override
    public byte[] downloadCode(String tableName) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        generatorCode(tableName, zip);
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * 生成代码（自定义路径）
     *
     * @param tableName 表名称
     */
    @Override
    public void generatorCode(String tableName) {
        GenTable table = findGenTableByName(tableName);
        GenTableVo tableVo = new GenTableVo();
        BeanUtils.copyProperties(table, tableVo);
        //设置列信息
        setColumnTable(tableVo);
        // 设置主子表信息
        setSubTable(tableVo);
        // 设置主键列信息
        setPkColumn(tableVo);

        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(tableVo);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "index.vue.vm", "index-tree.vue.vm")) {
                // 渲染模板
                StringWriter sw = new StringWriter();
                Template tpl = Velocity.getTemplate(template, Constant.UTF8);
                tpl.merge(context, sw);
                try {
                    String path = getGenPath(tableVo, template);
                    FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8);
                } catch (IOException e) {
                    throw new ServiceException("渲染模板失败，表名：" + table.getTableName());
                }
            }
        }
    }

    /**
     * 根据表名查询
     *
     * @param tableName
     * @return
     */
    private GenTable findGenTableByName(String tableName) {
        Example example = new Example(GenTable.class);
        Criteria criteria = example.createCriteria();
        criteria.andEqualTo("yn", Constant.YES);
        // 查询条件
        criteria.andEqualTo("tableName", tableName);
        List<GenTable> tableList = genTableMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(tableList)) {
            throw new ServiceException("非法请求");
        }
        // 查询表信息
        GenTable table = tableList.get(0);
        return table;
    }

    /**
     * 同步数据库
     *
     * @param tableName 表名称
     */
    @Override
    @Transactional(readOnly = false)
    public void synchDb(String tableName) {
        GenTable table = findGenTableByName(tableName);
        GenTableVo tableVo = new GenTableVo();
        BeanUtils.copyProperties(table, tableVo);
        List<GenTableColumnVo> tableColumns = tableVo.getColumns();
        List<String> tableColumnNames = tableColumns.stream().map(GenTableColumn::getColumnName)
                .collect(Collectors.toList());

        List<GenTableColumnVo> dbTableColumns = genTableColumnMapper.findDbTableColumnsByName(tableName);
        if (StringUtils.isEmpty(dbTableColumns)) {
            throw new ServiceException("同步数据失败，原表结构不存在");
        }
        List<String> dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName)
                .collect(Collectors.toList());

        dbTableColumns.forEach(column -> {
            if (!tableColumnNames.contains(column.getColumnName())) {
                GenUtils.initColumnField(column, table);
                column.setId(UUIDUtils.getUUID());
                genTableColumnMapper.insertSelective(column);
            }
        });

        List<GenTableColumn> delColumns = tableColumns.stream()
                .filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(delColumns)) {

            List<String> cids = delColumns.stream().map(dcol -> dcol.getId()).collect(Collectors.toList());
            for (String id : cids) {
                genTableColumnMapper.deleteByPrimaryKey(id);
            }
        }
    }

    /**
     * 批量生成代码（下载方式）
     *
     * @param tableNames 表数组
     * @return 数据
     */
    @Override
    public byte[] downloadCode(String[] tableNames) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        for (String tableName : tableNames) {
            generatorCode(tableName, zip);
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }

    /**
     * 查询表信息并生成代码
     */
    private void generatorCode(String tableName, ZipOutputStream zip) {
        // 查询表信息
        GenTable table = findGenTableByName(tableName);
        GenTableVo tableVo = new GenTableVo();
        BeanUtils.copyProperties(table, tableVo);
        //设置列信息
        setColumnTable(tableVo);
        // 设置主子表信息
        setSubTable(tableVo);
        // 设置主键列信息
        setPkColumn(tableVo);

        VelocityInitializer.initVelocity();

        VelocityContext context = VelocityUtils.prepareContext(tableVo);

        // 获取模板列表
        List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory());
        for (String template : templates) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, Constant.UTF8);
            tpl.merge(context, sw);
            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(VelocityUtils.getFileName(template, tableVo)));
                IOUtils.write(sw.toString(), zip, Constant.UTF8);
                IOUtils.closeQuietly(sw);
                zip.flush();
                zip.closeEntry();
            } catch (IOException e) {
                logger.error("渲染模板失败，表名：" + table.getTableName(), e);
            }
        }
    }

    /**
     * 修改保存参数校验
     *
     * @param genTable 业务信息
     */
    @Override
    public void validateEdit(GenTableVo genTable) {
        if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
            if (StringUtils.isEmpty(genTable.getTreeCode())) {
                throw new ServiceException("树编码字段不能为空");
            } else if (StringUtils.isEmpty(genTable.getTreeParentCode())) {
                throw new ServiceException("树父编码字段不能为空");
            } else if (StringUtils.isEmpty(genTable.getTreeName())) {
                throw new ServiceException("树名称字段不能为空");
            } else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) {
                if (StringUtils.isEmpty(genTable.getSubTableName())) {
                    throw new ServiceException("关联子表的表名不能为空");
                } else if (StringUtils.isEmpty(genTable.getSubTableFkName())) {
                    throw new ServiceException("子表关联的外键名不能为空");
                }
            }
        }
    }

    /**
     * 设置主键列信息
     *
     * @param table 业务表信息
     */
    public void setPkColumn(GenTableVo table) {
        for (GenTableColumnVo column : table.getColumns()) {
            if (column.isPk()) {
                table.setPkColumn(column);
                break;
            }
        }
        if (table.getPkColumn() == null) {
            table.setPkColumn(table.getColumns().get(0));
        }
        if (GenConstants.TPL_SUB.equals(table.getTplCategory())) {
            for (GenTableColumnVo column : table.getSubTable().getColumns()) {
                if (column.isPk()) {
                    table.getSubTable().setPkColumn(column);
                    break;
                }
            }
            if (StringUtils.isNull(table.getSubTable().getPkColumn())) {
                table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0));
            }
        }
    }

    /**
     * 设置主子表信息
     *
     * @param table 业务表信息
     */
    public void setSubTable(GenTableVo table) {
        String subTableName = table.getSubTableName();
        if (StringUtils.isNotEmpty(subTableName)) {
            GenTable tableSub = findGenTableByName(subTableName);
            GenTableVo tableVo = new GenTableVo();
            BeanUtils.copyProperties(tableSub, tableVo);
            table.setSubTable(tableVo);
        }
    }

    /**
     * 设置代码生成其他选项值
     *
     * @param genTable 设置后的生成对象
     */
    public void setTableFromOptions(GenTableVo genTable) {
        JSONObject paramsObj = JSONObject.parseObject(genTable.getOptions());
        if (StringUtils.isNotNull(paramsObj)) {
            String treeCode = paramsObj.getString(GenConstants.TREE_CODE);
            String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE);
            String treeName = paramsObj.getString(GenConstants.TREE_NAME);
            String parentMenuId = paramsObj.getString(GenConstants.PARENT_MENU_ID);
            String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME);

            genTable.setTreeCode(treeCode);
            genTable.setTreeParentCode(treeParentCode);
            genTable.setTreeName(treeName);
            genTable.setParentMenuId(parentMenuId);
            genTable.setParentMenuName(parentMenuName);
        }
    }

    @Override
    public List<GenTable> findAll() {
        return genTableMapper.selectAll();
    }

    @Override
    @Transactional(readOnly = false)
    public void deleteGenTableByIds(String[] tableIds) {
        for (String id : tableIds) {
            genTableMapper.deleteByPrimaryKey(id);
        }
    }

}
